package org.gwtEss.table;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.gwtEss.client.GwtEss;
import org.gwtEss.gwtExtensions.EnhancedFlexTable;
import org.gwtEss.gwtExtensions.FlexEventsTable;
import org.gwtEss.menu.MenuItem;
import org.gwtEss.table.SystemManagedColWidget.UserTriggerEvent;
import org.gwtEss.table.pages.ScrollPageNavigation.ScrollNavWidget;
import org.gwtEss.utils.Helper;

import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.HasChangeHandlers;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.HTMLTable.RowFormatter;

public abstract class Tab<VO> extends Composite implements ScrollNavWidget, HasRowCellHilite<VO>, HasRowCellClick<VO>{
//	private FlexEventsTable tab;
	private EnhancedFlexTable tab;
//	private int numOfRows=0;
	private Cols<VO> cols;
//	private int[] colsWidth; 
	private UserManagedWidgetInstanceCache<VO> userManagedInstanceCache = new UserManagedWidgetInstanceCache<VO>();
	private ArrayList<VO> rowVos; 
	public final static int ROW_HEIGHT = 25;
	public Tab(/*int numOfRows, */Cols<VO> cols) {
//		this.numOfRows = numOfRows;
		this.cols = cols;
//		colsWidth = new int[cols.size()];
		init();
		initWidget(tab);
	}
	int userRowClick = -1, userColClick = -1;
	int lastHiliteRow = -1, lastHiliteCol = -1; 
	VO prevData;
	private CellFormatter cellFormatter;
	private RowFormatter rowFormatter; 
	private void doRowCellHilite(int row, int column, ColWidget colWidget, VO data) {
		if (row == lastHiliteRow && column ==lastHiliteCol) return;
		
		VO lastRowData = null; 
		if (row != lastHiliteRow){
			if (lastHiliteRow != -1 && onBeforeRowUnHilite(lastRowData = getRowData(lastHiliteRow))){
				unHiliteRow(lastHiliteRow);
				onAfterRowUnHilite(lastRowData);
			}
			if (onBeforeRowHilite(data)){
				hiliteRow(row);
				onAfterRowHilite(data);
			}
		}
//		if (column != lastHiliteCol){
			ColWidget lastColWidget;
//			lastRowData = (lastRowData == null)?lastRowData = getRowData(lastHiliteRow):lastRowData ;
			if (lastHiliteRow != -1 && lastHiliteCol != -1 && onBeforeCellUnHilite(lastRowData = getRowData(lastHiliteRow), lastColWidget = getColWidget(lastHiliteCol))){
				unHiliteCell(lastHiliteRow, lastHiliteCol);
				onAfterCellUnHilite(lastRowData, lastColWidget);
			}
			if (onBeforeCellHilite(data, colWidget)){
				hiliteCell(row, column);
				onAfterCellHilite(data, colWidget);
			}
//		}
		lastHiliteRow = row;
		lastHiliteCol = column;
//		removeRowColHilite();
//		boolean continueRowCellHilite = onBeforeRowCellHilite(colWidget, data);
//		if (continueRowCellHilite){
//			tab.getRowFormatter().addStyleName(row, "table-row-move");
//			cellFormatter.addStyleName(row, column, "table-cell-move");
//			lastHiliteCol = column; 
//			lastHiliteRow = row;
//			onAfterRowCellHilite(colWidget, data);
//		}
	}
	private void hiliteCell(int row, int column) {
		cellFormatter.addStyleName(row, column, "table-cell-move");
	}
	private void unHiliteCell(int row, int column) {
		cellFormatter.removeStyleName(row, column, "table-cell-move");
	}
	private void unHiliteRow(int row) {
		rowFormatter.removeStyleName(row, "table-row-move");
	}
	private void hiliteRow(int row) {
		rowFormatter.addStyleName(row, "table-row-move");
	}

	/**
	 * Remove mouse move Hilite. 
	 */
	public void removeRowColHilite() {
		if (lastHiliteRow != -1 && lastHiliteCol != -1){
			unHiliteRow(lastHiliteRow);
			unHiliteCell(lastHiliteRow, lastHiliteCol);
			lastHiliteRow = lastHiliteCol = -1;
		}
	}
//	public void removeRowColHilite(){
//		if (lastHiliteRow != -1 && lastHiliteCol != -1){
//			ColWidget lastHiliteColWidget = getColWidget(lastHiliteCol);
//			VO lastHiliteVo = getVO(lastHiliteRow); 
//			onBeforeRowCellUnHilite(lastHiliteColWidget, lastHiliteVo);
//			tab.getRowFormatter().removeStyleName(lastHiliteRow, "table-row-move");
//			cellFormatter.removeStyleName(lastHiliteRow, lastHiliteCol, "table-cell-move");
//			onAfterRowCellUnHilite(lastHiliteColWidget, lastHiliteVo);
//			lastHiliteRow = lastHiliteCol = -1;
//		} 
//	}
	
	public ColWidget getColWidget(int column){
		return cols.get(column); 
	}
	public VO getRowData(int row){
		return rowVos.get(row);
	}
	
/*
	private void removeColumnStyle(int colNum, String style){
		
		for (int i=0; i<numOfRows; i++){
			tab.getCellFormatter().removeStyleName(i, colNum, style);
		}
	}
	
	private void addColumnStyle(int colNum, String style){
		
		for (int i=0; i<numOfRows; i++){
			tab.getCellFormatter().addStyleName(i, colNum, style);
		}
	}*/
	private void init() {
//		tab = new FlexEventsTable();
		tab = new EnhancedFlexTable();
//		smartUserManagedColWidgets = new SmartUserManagedColWidget[numOfRows];
		tab.setCellPadding(0);
		tab.setCellSpacing(0);
//		tab.setBorderWidth(1);
		cellFormatter = tab.getCellFormatter();
		rowFormatter = tab.getRowFormatter();
/*		for (int c=0; c<cols.size(); c++)
			if (cols.get(c).isVisible())
			for (int r = 0; r < numOfRows; r++){
//				tab.setWidget(r, c, new Div("loading..."));
				cellFormatter.setHeight(r, c, ROW_HEIGHT + "px");
			}*/
		rowVos = new ArrayList<VO>();
/*		for (int r = 0; r < numOfRows; r++){
			rowVos.add(null);
			tab.getRowFormatter().addStyleName(r, r % 2 ==0? "table-row-odd": "table-row-even");	
		}*/

		
		tab.addTableMouseListener(new TableMouseListenerAdapter(){

			@Override
			public void onCellMouseMove(Event event, int row, int column) {
////				doMouseEvent(event, row, column, true);
//				ColWidget colWidget = cols.get(column);
////				if (userRowClick == row && userColClick == column)//User clicks the same cell again
////					return;
//				ColWidgetType colWidgetType = ColWidgetHelper.getColWidgetType(colWidget);
//				UserTriggerEvent flipWidgetEvent = ((SystemManagedColWidget<VO, ?>)colWidget).flipWidgetEvent(); 
//				if (flipWidgetEvent == UserTriggerEvent.MOUSE_CLICK){
//					doFlipSystemManagedWidget(row, column, (SystemManagedColWidget<VO, ?>) colWidget);
//				}
////				doMouseEvent(event, row, column, false);
				
				ColWidget<VO> colWidget = cols.get(column);
				doRowCellHilite(row, column, colWidget, getRowData(row));				
				if (getUserTriggerEvent(colWidget) == UserTriggerEvent.MOUSE_MOVE)
					doFlipSystemManagedWidget(row, column, (SystemManagedColWidget) colWidget);
			}
			

			
			@Override
			public void onCellMouseClick(Event event, int row, int column) {
				
				ColWidget colWidget = cols.get(column);
				VO rowData = getRowData(row);
				onRowCellClick(rowData, colWidget);
				if (userRowClick == row && userColClick == column)//User clicks the same cell again
					return;
				if (getUserTriggerEvent(colWidget) == UserTriggerEvent.MOUSE_CLICK)
					doFlipSystemManagedWidget(row, column, (SystemManagedColWidget) colWidget);
//				doMouseEvent(event, row, column, false);
			}
		});
	}
	private UserTriggerEvent getUserTriggerEvent(ColWidget<VO> colWidget){
		if (colWidget instanceof SystemManagedColWidget)
			return ((SystemManagedColWidget)colWidget).flipWidgetEvent();
		return null; 
	}
//	private void doMouseEvent(Event event, int row, int column){
//		ColWidget colWidget = cols.get(column);
//		if (userRowClick == row && userColClick == column)//User clicks the same cell again
//			return;
//		if (move)
//			doRowCellHilite(row, column, colWidget, getRowData(row));				
//		doFlipWidget();
//	}	
//	private void doFlipWidget(ColWidget colWidget, int row, int column, UserTriggerEvent event){
//		ColWidgetType colWidgetType = ColWidgetHelper.getColWidgetType(colWidget);
//		if (
//				colWidgetType == ColWidgetType.SYSTEM_MANAGED && 
//				((SystemManagedColWidget<VO, ?>)colWidget).flipWidgetEvent() == /*(move?UserTriggerEvent.MOUSE_MOVE:UserTriggerEvent.MOUSE_CLICK)*/){
//			doFlipSystemManagedWidget(row, column, (SystemManagedColWidget<VO, ?>) colWidget);
//		}/*else if (colWidgetType == ColDefType.USER_MANAGED){
//			//Do something if required. 
//		}*/
//	}
	private void doFlipSystemManagedWidget(int row, int column, SystemManagedColWidget<VO, ?> systemManagedCol){
		
			Widget w;	
			removePreviousWidgetIfAny();				
//			VO data = (VO)rowVos[row];//TODO: What if data does not exist in the list? Where is the check?
			VO data = rowVos.get(row);
			w = (Widget)systemManagedCol.getWidget();
			renderCell(row, column, w);
			systemManagedCol.doResetWidget(data);
			prevData = data;
			userColClick = column;
			userRowClick = row; 
		
	
	}
	private void removePreviousWidgetIfAny(){
		if (userRowClick != -1 && userColClick != -1){
			ColWidget prevColWidget = cols.get(userColClick);
			ColWidgetType prevColWidgetType = ColWidgetHelper.getColWidgetType(prevColWidget);
			if (prevColWidgetType == ColWidgetType.SYSTEM_MANAGED){
				Widget prevW;			
				prevW = tab.getWidget(userRowClick, userColClick);
				if (prevW != null){
					((SystemManagedColWidget)prevColWidget).doResetData(prevData);
					tab.remove(prevW);
					
					TextDesc displayTxt = ((SystemManagedColWidget)prevColWidget).createDisplayText(prevData);
					renderCell(userRowClick, userColClick, displayTxt);
				}
			}
		}
	}

	protected void renderCell(int r, int c, Widget w){
		tab.setWidget(r, c, w);	
	}
	protected void renderCell(int r, int c, TextDesc textDesc){
//		TextDesc resizedTxt = Helper.getTxtBasedOnPx(textDesc, cols.get(c).getColWidth());
//		tab.setText(r, c, resizedTxt.getText());
//		System.out.println("row num: " + r);
		tab.setText(r, c, textDesc.getText());
	}
	private Widget retrieveUserManagedWidgetInstance(VO data, UserManagedColWidget<VO, ?> userManagedCol){
		Widget w; 
		if ((w = userManagedInstanceCache.get(data, userManagedCol)) == null)
			userManagedInstanceCache.put(
					data, 
					userManagedCol, 
					w = (Widget)userManagedCol.createColWidgetInstance(data));//if createColWidgetInstance is not Widget type, thrown an exception
		return w; 
	}

	
	private void ensureArrayCapacity(ArrayList<?> arr, int index){
		if (index >= arr.size())
			for (int i=0; i < (index - arr.size() + 1 ); i++)
				arr.add(null);
	}
	
	public void render(int row, Object obj) {
		VO data = (VO) obj;
		ensureArrayCapacity(rowVos, row);
		rowVos.set(row, data);
		ColWidget colWidget;
		TextDesc displayTxt;
		Widget w;
		ColWidgetType colWidgetType;
		boolean firstTimeRender = tab.getRowCount() <= row;
		int colSpan = 1; 
		for (int c=0; c<cols.size(); c+=colSpan){
			colWidget = cols.get(c);
			if (colWidget.isVisible()){
				colSpan = colWidget.colSpan(data);
				if (colSpan > 1)
					tab.getFlexCellFormatter().setColSpan(row, c, colSpan);
				cellFormatter.setHorizontalAlignment(row, c, colWidget.alignment(data));
				cellFormatter.getElement(row, c).setTitle(colWidget.title(data));
				colWidgetType = ColWidgetHelper.getColWidgetType(colWidget);
				if (colWidgetType == ColWidgetType.USER_MANAGED){
					w = retrieveUserManagedWidgetInstance(data, (UserManagedColWidget<VO, ?>) colWidget);
					renderCell(row, c, w);
				}else if (colWidgetType == ColWidgetType.SYSTEM_MANAGED){
					displayTxt = ((SystemManagedColWidget<VO, ?>) colWidget).createDisplayText(data);
					renderCell(row, c, displayTxt);
				}else if (colWidgetType == ColWidgetType.READONLY_TEXT){
					displayTxt = ((ReadOnlyTextColWidget<VO>) colWidget).createDisplayText(data);
					renderCell(row, c, displayTxt);
				}else if (colWidgetType == ColWidgetType.SMART_USER_MANAGED){
					Widget widget = retrieveSmartUserManagedWidget(row, c, (SmartUserManagedColWidget)colWidget, data);
					renderCell(row, c, widget);
				}
			}
		}
		if (firstTimeRender)
			renderStyles(row);
	}
	private class SmartColWidgetChangeHandler implements ChangeHandler{
		private SmartUserManagedColWidget colWidget; 
		private VO data;
		private Widget w;
		public SmartColWidgetChangeHandler(SmartUserManagedColWidget colWidget,
				VO data, Widget w) {
			super();
			this.colWidget = colWidget;
			this.data = data;
			this.w = w;
		}
		public void onChange(ChangeEvent event) {
			colWidget.resetData(w, data);
		} 
		private void set(SmartUserManagedColWidget colWidget,
				VO data, Widget w){
			this.colWidget = colWidget;
			this.data = data;
			this.w = w;
		}
		
	}
	private HashMap<String , SmartColWidgetChangeHandler> smartColWidgetChangeHandlers = new HashMap<String , SmartColWidgetChangeHandler>();
	
	private Widget retrieveSmartUserManagedWidget(int row, int c, SmartUserManagedColWidget colWidget, VO data){
		Widget widget = tab.getWidget(row, c);
		boolean isDataReset = false; 
		if (widget == null){
			widget = ((SmartUserManagedColWidget)colWidget).createColWidgetInstance();
			if (widget instanceof HasChangeHandlers){
				isDataReset = true; 
				SmartColWidgetChangeHandler changeHandler = new SmartColWidgetChangeHandler(colWidget, data, widget);
				((HasChangeHandlers) widget).addChangeHandler(changeHandler);
				smartColWidgetChangeHandlers.put(row + "", changeHandler);
			};
		}
		if (!isDataReset && widget instanceof HasChangeHandlers){
			SmartColWidgetChangeHandler changeHandler = smartColWidgetChangeHandlers.get(row+"");
			changeHandler.set(colWidget, data, widget);
		}
			
		colWidget.resetWidget(data, widget);
		return widget;
	}

	private void resizeCol(int col, int colWidth) {
		System.out.println("Visible col:" + col + ", col width: " + colWidth);
//		for (int i=0; i<numOfRows; i++)
//			cellFormatter.setWidth(i, col, colWidth + "px");
		tab.setColumnWidth(col, colWidth);
		
		ColWidget colWidget = cols.get(col);
		ColWidgetType colWidgetType = ColWidgetHelper.getColWidgetType(colWidget);
		
		if (colWidgetType == ColWidgetType.SYSTEM_MANAGED)
			resizeSystemManagedCol((SystemManagedColWidget<VO, ?>)colWidget, colWidth, col);
		else if (colWidgetType == ColWidgetType.USER_MANAGED)
			resizeUserManagedCol((UserManagedColWidget<VO, ?>)colWidget, colWidth, col);
//		else if (colWidgetType == ColWidgetType.READONLY_TEXT)
//			resizeReadOnlyCol((ReadOnlyTextColWidget<VO>)colWidget, colWidth, col);
		else if (colWidgetType == ColWidgetType.SMART_USER_MANAGED)
			resizeSmartUserManagedCol((SmartUserManagedColWidget<VO, ?>)colWidget, colWidth, col);
	}
	private void resizeSmartUserManagedCol(
			SmartUserManagedColWidget<VO, ?> colWidget, int pxWidth, int col) {
		for (int r=0; r< rowVos.size(); r++){
			Widget w = tab.getWidget(r, col);
			if (w != null)
				w.setWidth(pxWidth + "px");
		}
	}

/*	private void resizeReadOnlyCol(ReadOnlyTextColWidget<VO> colWidget,
			int pxWidth, int col) {
		for (int r=0; r< numOfRows; r++){
			VO data = rowVos.get(r);
			if (data != null)
				renderCell(r, col, colWidget.createDisplayText(data));//TODO: This can be optimized by storing the text in cache
		}
	}
*/
	private void resizeUserManagedCol(UserManagedColWidget<VO, ?> userColW,
			int pxWidth, int col) {
		if (userColW != null){
			userManagedInstanceCache.setWidth(pxWidth, userColW);
		}
	}

	private void resizeSystemManagedCol(SystemManagedColWidget<VO, ?> systemColW, int pxWidth, int col){
		VO vo; 
		if (systemColW != null){
			systemColW.changeWidgetWidth(pxWidth);
			/*for (int i=0; i<rowVos.size(); i++ ){
				vo = rowVos.get(i);
				if (vo != null){
					TextDesc displayTxt = systemColW.createDisplayText(vo);
					renderCell(i, col, displayTxt);
				}
				
			}*/
		}
	}
	/**
	 * Styles will be rendered only the very first time table is rendered. 
	 * This means if the user changes isAlternateRowColor() flag, he will have to not only re render, but re init the table
	 * @param row
	 */
	private void renderStyles(int row) {
		if(isAlternateRowColor())
		tab.getRowFormatter().addStyleName(row, row % 2 ==0? "table-row-odd": "table-row-even");
		for (int c=0; c<cols.size(); c++){
			cellFormatter.setHeight(row, c, ROW_HEIGHT + "px");
//			tab.setText(row, c, ".");
		}
	}
	/*public void renderBlank(int row) {
		for (int c=0; c<cols.size(); c++){
			tab.setText(row, c, ".");
		}
	}*/

	public int getHeight() {
		return rowVos.size()*ROW_HEIGHT;
	}

	
	public void resizeCols() {
		ColWidget col;
		for (int i=0; i< cols.size(); i++){
			col = cols.get(i);
			if (col.isVisible()){
				resizeCol(i, col.getColWidth());
			}
		}
	}
	public void setColVisibility(int colNum, Boolean visible) {
		for (int i=0; i< rowVos.size(); i++)
			cellFormatter.setVisible(i, colNum, visible);
	}
	public void removeRows(int from) {
//		tab.clear();
//		
		int rowVoSize = rowVos.size();
		for (int i=from; i<rowVoSize ; i++){
			rowVos.remove(rowVoSize-(i-from)-1);
		}
//		
//		int maxRowNum = tab.getRowCount(); 
//		for (int i=from; i<maxRowNum ; i++){
//			for (int j=0; j<cols.size(); j++)
//			tab.clearCell(i, j);
//		}
		
		int maxRowNum = tab.getRowCount(); 
		for (int i=from; i<maxRowNum; i++)
			tab.removeRow(maxRowNum-(i-from)-1);
		
		lastHiliteRow = lastHiliteRow >from?-1: lastHiliteRow;
	}

	/**
	 * TODO: Test this method from OnDemand table where there are pages. 
	 * @param vo
	 * @return
	 */
	public int getRowAbsoluteTop(VO vo) {
		int voIndex = rowVos.indexOf(vo);
		int tabRowCount = tab.getRowCount(); 
		if (voIndex != -1 && voIndex < tabRowCount){
			return tab.getRowFormatter().getElement(voIndex).getAbsoluteTop();
		}
		return -1;
	}
}
